/*
 * ========================================================================
 * Copyright 2008 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

package ws;

import com.microsoft.schemas.exchange.services._2006.messages.ExchangeServicePortType;
import com.microsoft.schemas.exchange.services._2006.messages.ExchangeServices;
import com.microsoft.schemas.exchange.services._2006.messages.FindItemResponseMessageType;
import com.microsoft.schemas.exchange.services._2006.messages.FindItemResponseType;
import com.microsoft.schemas.exchange.services._2006.messages.FindItemType;
import com.microsoft.schemas.exchange.services._2006.messages.GetItemResponseType;
import com.microsoft.schemas.exchange.services._2006.messages.GetItemType;
import com.microsoft.schemas.exchange.services._2006.messages.ItemInfoResponseMessageType;
import com.microsoft.schemas.exchange.services._2006.messages.ResponseMessageType;
import com.microsoft.schemas.exchange.services._2006.types.CalendarItemType;
import com.microsoft.schemas.exchange.services._2006.types.CalendarItemTypeType;
import com.microsoft.schemas.exchange.services._2006.types.DefaultShapeNamesType;
import com.microsoft.schemas.exchange.services._2006.types.DistinguishedFolderIdNameType;
import com.microsoft.schemas.exchange.services._2006.types.DistinguishedFolderIdType;
import com.microsoft.schemas.exchange.services._2006.types.EmailAddressType;
import com.microsoft.schemas.exchange.services._2006.types.ExchangeVersionType;
import com.microsoft.schemas.exchange.services._2006.types.ItemIdType;
import com.microsoft.schemas.exchange.services._2006.types.ItemQueryTraversalType;
import com.microsoft.schemas.exchange.services._2006.types.ItemResponseShapeType;
import com.microsoft.schemas.exchange.services._2006.types.ItemType;
import com.microsoft.schemas.exchange.services._2006.types.NonEmptyArrayOfBaseFolderIdsType;
import com.microsoft.schemas.exchange.services._2006.types.NonEmptyArrayOfBaseItemIdsType;
import com.microsoft.schemas.exchange.services._2006.types.OccurrenceInfoType;
import com.microsoft.schemas.exchange.services._2006.types.RequestServerVersion;
import com.microsoft.schemas.exchange.services._2006.types.ResponseClassType;
import com.microsoft.schemas.exchange.services._2006.types.ServerVersionInfo;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Holder;
import net.fortuna.ical4j.model.DateTime;
import org.apache.log4j.Logger;

/**
 *
 * @author nickchen
 */
public class ExchangeCalendarRetriever {

    private static Logger  logger = Logger.getLogger( ExchangeCalendarRetriever.class );
    private ExchangeServices        service;
    private ExchangeServicePortType port;
    private RequestServerVersion    requestVersion;
    Holder<ServerVersionInfo>       serverVersion;
    
    /**
     * 
     * @param baseURL - url of local file system where servlet webapp resides
     * @param domain
     * @param username
     * @param password
     * @throws java.net.MalformedURLException
     */
    public ExchangeCalendarRetriever(URL baseURL, String domain, String username, String password) throws MalformedURLException {
        URL url             = new URL(baseURL, "../wsdl/Services.wsdl");
        service             = new ExchangeServices( url,  new QName("http://schemas.microsoft.com/exchange/services/2006/messages", "ExchangeServices"));
        port                = service.getExchangeServicePort();
        requestVersion      = new RequestServerVersion();
        serverVersion       = new Holder<ServerVersionInfo>();

        ((BindingProvider) port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, domain +"\\"+ username);
        ((BindingProvider) port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, password);

        requestVersion.setVersion(ExchangeVersionType.EXCHANGE_2007_SP_1);
        
    }
    
    public ArrayList<CalendarItemType> getCalender(String exchangeUserEmail, String b, String a) throws com.sun.xml.ws.client.ClientTransportException, Exception {
        Integer daysBack = 30;
        Integer daysAhead = 180;
        if ( a != null ) {
            try {
                daysAhead = Integer.parseInt(a.trim());
            } catch (NumberFormatException nfe) {
                Exception ex;
                ex = new Exception( "NumberFormatException: " + nfe.getMessage() );
                throw( ex );
            }
        }
        if ( b != null ) {
            try {
                daysBack = Integer.parseInt(b.trim());
            } catch (NumberFormatException nfe) {
                Exception ex;
                ex = new Exception( "NumberFormatException: " + nfe.getMessage() );
                throw( ex );
            }
        }
        java.util.GregorianCalendar calStartDate = new java.util.GregorianCalendar();
        calStartDate.setTime( new DateTime() );
        calStartDate.add(java.util.GregorianCalendar.DATE, -daysBack);
        java.util.GregorianCalendar calEndDate = new java.util.GregorianCalendar();
        calEndDate.setTime( new DateTime() );
        calEndDate.add(java.util.GregorianCalendar.DATE, daysAhead);

        logger.debug("Requested date range "+ getStrDate(calStartDate) +" to "+ getStrDate(calEndDate) );

        FindItemType request            = new FindItemType();
        request.setTraversal(ItemQueryTraversalType.SHALLOW);

        ItemResponseShapeType itemShape = new ItemResponseShapeType();
        itemShape.setBaseShape(DefaultShapeNamesType.ALL_PROPERTIES);

        // seems like this should work but it doesn't
        //itemShape.setBodyType(BodyTypeResponseType.TEXT);
        request.setItemShape(itemShape);

        DistinguishedFolderIdType calendarFolder = new DistinguishedFolderIdType();
        calendarFolder.setId(DistinguishedFolderIdNameType.CALENDAR);

        if ( exchangeUserEmail != null ) {
            EmailAddressType exchangeUser = new EmailAddressType();
            exchangeUser.setEmailAddress(exchangeUserEmail);
            calendarFolder.setMailbox(exchangeUser);
        }

        NonEmptyArrayOfBaseFolderIdsType folderIds = new NonEmptyArrayOfBaseFolderIdsType();
        folderIds.getFolderIdOrDistinguishedFolderId().add(calendarFolder);
        request.setParentFolderIds(folderIds);

        requestVersion.setVersion(ExchangeVersionType.EXCHANGE_2007);
        Holder<FindItemResponseType> findItemResult = new Holder<FindItemResponseType>();
        port.findItem(request, requestVersion, findItemResult, serverVersion);

        List<JAXBElement<? extends ResponseMessageType>> responses = null;
        responses = findItemResult.value.getResponseMessages().getCreateItemResponseMessageOrDeleteItemResponseMessageOrGetItemResponseMessage();
        ArrayList<CalendarItemType> calendarItems = new ArrayList<CalendarItemType>();
        for (JAXBElement<? extends ResponseMessageType> jaxResponse : responses) {
            ResponseMessageType response = jaxResponse.getValue();
            if (response.getResponseClass().equals(ResponseClassType.SUCCESS)) {
                FindItemResponseMessageType findResponse = (FindItemResponseMessageType) response;

                ArrayList<CalendarItemType> originalItems = new ArrayList<CalendarItemType>();
                ArrayList<ItemIdType> itemIds = new ArrayList<ItemIdType>();
                ArrayList<ItemIdType> modifiedOccurrenceItemIds = new ArrayList<ItemIdType>();

                for (ItemType item : findResponse.getRootFolder().getItems().getItemOrMessageOrCalendarItem()) {
                    if( item.getClass().equals(CalendarItemType.class) ) {
                        CalendarItemType calendarItem   = (CalendarItemType) item;
                        try {
                            logger.debug("Item (1): "+calendarItem.getSubject()+":"+calendarItem.getCalendarItemType().toString());

                            if( calendarItem.getCalendarItemType().equals(CalendarItemTypeType.RECURRING_MASTER) ) {
                                // to limit the size of the iCalendar file let the user limit the list of calendarItems
                                Boolean beforeEndDate = false;
                                Boolean afterStartDate = false;
                                if ( calendarItem.getLastOccurrence() != null ) {
                                    if ( calStartDate.before(calendarItem.getLastOccurrence().getEnd().toGregorianCalendar()) ) {
                                        afterStartDate = true;
                                        logger.debug(calendarItem.getLastOccurrence().getEnd().toGregorianCalendar());
                                    }
                                } else {
                                    logger.debug("getEnd is null");
                                    afterStartDate = true;
                                }
                                if ( calendarItem.getFirstOccurrence() != null ) {
                                    if ( calEndDate.after(calendarItem.getFirstOccurrence().getStart().toGregorianCalendar()) ) {
                                        beforeEndDate = true;
                                        logger.debug(calendarItem.getFirstOccurrence().getStart().toGregorianCalendar());
                                    }
                                } else {
                                    logger.debug("getStart is null");
                                    beforeEndDate = true;
                                }
                                
                                if( afterStartDate && beforeEndDate ) {  // is this item in the the date range we want?
                                    itemIds.add(calendarItem.getItemId());
////                                    originalItems.add( calendarItem );
                                }
                            }
                            else if( calendarItem.getCalendarItemType().equals(CalendarItemTypeType.SINGLE) ) {
                                // to limit the size of the iCalendar file let the user limit the list of calendarItems
                                if( calStartDate.before(calendarItem.getEnd().toGregorianCalendar()) && calEndDate.after(calendarItem.getStart().toGregorianCalendar()) ) {
                                    logger.debug("item included");
                                    itemIds.add(calendarItem.getItemId());
                                }
                                else {
                                    logger.debug("item excluded: date is out of range");
                                }
                            }
                            else {  // not sure what type of item this might be, but handle it anyhow
                                // to limit the size of the iCalendar file let the user limit the list of calendarItems
                                if( calStartDate.before(calendarItem.getEnd().toGregorianCalendar()) && calEndDate.after(calendarItem.getStart().toGregorianCalendar()) ) {
                                    itemIds.add(calendarItem.getItemId());
                                }
                            }
                        } catch (Exception ex) {
                            logger.error(ex.getMessage(), ex);
                        }
                    }
                }

                // make a batch request for CalendarItems
                if (itemIds.size() < 1) {
                    logger.debug("no items to retrieve");
                }
                ArrayList<CalendarItemType> items = getCalendarItems(itemIds);
                logger.debug("processing "+ items.size() + " items");
                for ( CalendarItemType item : items ) {
/*
                    for ( CalendarItemType originalItem : originalItems ) {
                        if ( originalItem.equals(item) ) {
                            originalItems.remove(originalItem);
                        }
                    }
*/
                    logger.debug("processing an item " + item.getSubject() );
                    CalendarItemType calendarItem = (CalendarItemType) item;
                    calendarItems.add( calendarItem );
                    logger.debug("  added to calendar" );
                    if( calendarItem.getCalendarItemType().equals(CalendarItemTypeType.RECURRING_MASTER) ){
                        logger.debug("  this item is a recurring master");
                        if ( calendarItem.getModifiedOccurrences() != null ){ // for recurring events we need to request any occurences that have been modified
                            logger.debug("  this item has modified occurrences");
                            for( OccurrenceInfoType o : calendarItem.getModifiedOccurrences().getOccurrence() ) {
                                modifiedOccurrenceItemIds.add( o.getItemId() );
                            }
                        }
                    }
                }
/*                // add any remaining original items to the calendar
                for ( CalendarItemType originalItem : originalItems ) {
                    if ( ! calendarItems.contains(originalItem) ) {
                        logger.debug("reprocessing an originalItem " + originalItem.getSubject() );
                        calendarItems.add( originalItem );
                    }
                }
*/

                // make a second batch request for modified occurences CalendarItems
                if (modifiedOccurrenceItemIds.size() < 1) {
                    logger.debug("no modified occurences to fetch");
                }
                else {
                    items.clear();
                    items = getCalendarItems(modifiedOccurrenceItemIds);
                    for ( CalendarItemType item : items ) {
                        logger.debug("adding a modified occurence");
                        calendarItems.add( item );
                    }
                }

            }
            else if (response.getResponseClass().equals(ResponseClassType.WARNING)) {
                Exception ex;
                ex = new Exception( response.getResponseCode() +": "+ response.getMessageText() );
                throw( ex );
            }
            else if (response.getResponseClass().equals(ResponseClassType.ERROR)) {
                Exception ex;
                ex = new Exception( response.getResponseCode() +": "+ response.getMessageText() );
                throw( ex );
            }
        }

        logger.debug("\n----------\nThere are "+ calendarItems.size() + " calendarItems\n--------");
        
        return calendarItems;
    }
    
    public ArrayList<CalendarItemType> getCalendarItems( ArrayList<ItemIdType> itemId_list ) {
        List<ItemType> items = null;

        if (itemId_list.size() < 1) {
            logger.debug("--- skipping request for "+ itemId_list.size() + " items ----");
            return null;
        }

        GetItemType getItemRequest                  = new GetItemType();
        ItemResponseShapeType itemResponseShapeType = new ItemResponseShapeType();
        itemResponseShapeType.setBaseShape( DefaultShapeNamesType.ALL_PROPERTIES );
        getItemRequest.setItemShape( itemResponseShapeType );

        NonEmptyArrayOfBaseItemIdsType itemIds = new NonEmptyArrayOfBaseItemIdsType();
        for ( ItemIdType itemId: itemId_list ) {
            itemIds.getItemIdOrOccurrenceItemIdOrRecurringMasterItemId().add( itemId );
        }
        getItemRequest.setItemIds( itemIds );
        
        Holder<GetItemResponseType> getItemResult = new Holder<GetItemResponseType>();

        logger.debug("--- sending request for "+ itemId_list.size() + " items ----");
        port.getItem(getItemRequest, requestVersion, getItemResult, serverVersion);

        GetItemResponseType getItemResponse = getItemResult.value;
        logger.debug("items retrieved: " + getItemResponse.getResponseMessages().getCreateItemResponseMessageOrDeleteItemResponseMessageOrGetItemResponseMessage().size());

        ArrayList<CalendarItemType> i = new ArrayList<CalendarItemType>();
        for ( Iterator<JAXBElement<? extends ResponseMessageType>> r = getItemResponse.getResponseMessages().getCreateItemResponseMessageOrDeleteItemResponseMessageOrGetItemResponseMessage().iterator(); r.hasNext(); ) {
            ResponseMessageType responseMessage = r.next().getValue();
            if (responseMessage.getResponseClass().equals(ResponseClassType.SUCCESS)) {
                ItemInfoResponseMessageType itemInfoResponse = (ItemInfoResponseMessageType) responseMessage;
                ItemType tmpItem = itemInfoResponse.getItems().getItemOrMessageOrCalendarItem().get(0);
                if( tmpItem != null && tmpItem.getClass().equals(CalendarItemType.class) ) {
                    i.add( (CalendarItemType) tmpItem );
                }
/*
                for ( Iterator<ItemType> it = itemInfoResponse.getItems().getItemOrMessageOrCalendarItem().iterator(); it.hasNext();) {
                    ItemType tmpItem = it.next();
                    if( tmpItem.getClass().equals(CalendarItemType.class) ) {
                        if ( tmpItem != null ) {
                            i.add( (CalendarItemType) tmpItem );
                        }
                    }
                }
*/
            }
        }
        logger.debug("valid items: " + i.size());
        return i;
    }
    
    public CalendarItemType getCalendarItem( ItemIdType itemId ) {
        GetItemType getItemRequest                  = new GetItemType();
        ItemResponseShapeType itemResponseShapeType = new ItemResponseShapeType();
        itemResponseShapeType.setBaseShape( DefaultShapeNamesType.ALL_PROPERTIES );
        getItemRequest.setItemShape( itemResponseShapeType );

        NonEmptyArrayOfBaseItemIdsType itemIds = new NonEmptyArrayOfBaseItemIdsType();
        itemIds.getItemIdOrOccurrenceItemIdOrRecurringMasterItemId().add( itemId );
        getItemRequest.setItemIds( itemIds );
        
        Holder<GetItemResponseType> getItemResult = new Holder<GetItemResponseType>();

        logger.debug("\n--- sending request for 1 single items ----\n");
        port.getItem(getItemRequest, requestVersion, getItemResult, serverVersion);

        GetItemResponseType getItemResponse = getItemResult.value;
        ResponseMessageType responseMessage = getItemResponse.getResponseMessages().getCreateItemResponseMessageOrDeleteItemResponseMessageOrGetItemResponseMessage().get(0).getValue();
        if (responseMessage.getResponseClass().equals(ResponseClassType.SUCCESS)) {
            ItemInfoResponseMessageType itemInfoResponse   = (ItemInfoResponseMessageType) responseMessage;
            return (CalendarItemType) itemInfoResponse.getItems().getItemOrMessageOrCalendarItem().get(0);
        }
        return null;
    }

    public static String getStrDate(java.util.GregorianCalendar c) {
        int m = c.get(java.util.GregorianCalendar.MONTH) + 1;
        int d = c.get(java.util.GregorianCalendar.DATE);
        String mm = Integer.toString(m);
        String dd = Integer.toString(d);
        return "" + c.get(java.util.GregorianCalendar.YEAR) + (m < 10 ? "0" + mm : mm) + (d < 10 ? "0" + dd : dd);
    }
    
}
